import * as Cesium from 'cesium'
import type { FieldMappings, FieldMappingsKey, ReturnBillboard, TypedBillboard } from '../types'

export class BillboardController {
  private _viewer: Cesium.Viewer
  private billboardCollection: Cesium.BillboardCollection
  private billboards: Map<string, TypedBillboard>
  private typeIndex: Map<string, Set<string>>
  private visibilityIndex: Map<boolean, Set<string>>

  // Billboard默认配置
  private defaultOptions: Partial<Cesium.Billboard.ConstructorOptions> = {
    show: true,
    width: 32,
    height: 32,
    horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  }

  // 字段映射
  private fieldMappings: FieldMappings = {
    id: 'id',
    jd: 'gzLon',
    wd: 'gzLat',
    height: 'height',
    image: 'image',
    label: 'name',
  }

  constructor(
    viewer: Cesium.Viewer,
    fieldMappings?: Partial<FieldMappings>,
    defaultOptions?: Partial<Cesium.Billboard.ConstructorOptions>
  ) {
    this._viewer = viewer
    this.defaultOptions = { ...this.defaultOptions, ...defaultOptions }
    this.fieldMappings = { ...this.fieldMappings, ...fieldMappings }
    this.billboardCollection = viewer.scene.primitives.add(new Cesium.BillboardCollection())
    this.billboards = new Map()
    this.typeIndex = new Map()
    this.visibilityIndex = new Map([
      [true, new Set()],
      [false, new Set()],
    ])
  }

  /**
   * 获取映射后的字段值
   * @param targetField
   * @param metadata
   * @returns
   */
  private getMappedValue(targetField: FieldMappingsKey, metadata: Record<string, any>): any {
    const sourceField = this.fieldMappings[targetField]
    if (!sourceField) {
      return metadata[targetField]
    }
    return metadata[sourceField]
  }

  /**
   * 设置字段映射
   * @param newFieldMappings
   */
  setFieldMappings(newFieldMappings: Partial<FieldMappings>): void {
    this.fieldMappings = { ...this.fieldMappings, ...newFieldMappings }
  }

  /**
   * 获取 Viewer 实例
   * @readonly
   * @type {Cesium.Viewer}
   * @memberof BillboardController
   */
  get viewer(): Cesium.Viewer {
    return this._viewer
  }

  /**
   * 添加单个 Billboard
   * @param metadata
   * @param options
   * @returns
   */
  add(metadata: Record<string, any>, options?: Cesium.Billboard.ConstructorOptions): void {
    const id = this.getMappedValue('id', metadata)
    if (this.billboards.has(id)) {
      return
    }
    // 合并默认选项和传入选项
    const mergedOptions: Cesium.Billboard.ConstructorOptions = {
      ...this.defaultOptions,
      position: Cesium.Cartesian3.fromDegrees(
        +this.getMappedValue('jd', metadata),
        +this.getMappedValue('wd', metadata),
        +this.getMappedValue('height', metadata) || 0
      ),
      image: this.getMappedValue('image', metadata),
      id,
      ...options,
    }
    const type = this.getMappedValue('type', metadata)
    const cesiumBillboard = this.billboardCollection.add(mergedOptions)
    const typedBillboard: TypedBillboard = {
      id,
      type,
      cesiumBillboard,
      label: this.getMappedValue('label', metadata),
      visible: mergedOptions.show !== false,
      metadata,
    }
    this.billboards.set(id, typedBillboard)
    this.addToIndices(id, type, typedBillboard.visible)
  }

  /**
   * 根据 id 获取 Billboard
   * @param id
   * @returns
   */
  get(id: string): ReturnBillboard {
    return this.billboards.get(id)
  }

  /**
   * 更新单个 Billboard
   * @param id
   * @param changes
   * @returns
   */
  update(
    id: string,
    changes: Partial<{
      type?: string
      visible?: boolean
      options?: Partial<Cesium.Billboard.ConstructorOptions>
      metadata?: Record<string, any>
    }>
  ): void {
    const billboard = this.get(id)
    if (!billboard) {
      console.warn(`Billboard with id ${id} not found`)
      return
    }

    const updatedFields: string[] = []
    const newBillboard = { ...billboard }

    if (changes.type !== undefined && changes.type !== billboard.type) {
      this.removeFromIndices(id, billboard.type, billboard.visible)
      newBillboard.type = changes.type
      updatedFields.push('type')
    }

    let visibilityChanged = false
    if (changes.visible !== undefined && changes.visible !== billboard.visible) {
      newBillboard.visible = changes.visible
      visibilityChanged = true
      updatedFields.push('visible')
    }

    if (changes.options) {
      Object.assign(billboard.cesiumBillboard, changes.options)
      updatedFields.push('options')

      if ('show' in changes.options) {
        const newVisibility = changes.options.show !== false
        if (newVisibility !== billboard.visible) {
          newBillboard.visible = newVisibility
          visibilityChanged = true
        }
      }
    }

    if (changes.metadata !== undefined) {
      newBillboard.metadata = changes.metadata
      updatedFields.push('metadata')
    }

    if (updatedFields.length > 0) {
      if (visibilityChanged) {
        billboard.cesiumBillboard.show = newBillboard.visible
      }

      if (changes.type !== undefined || visibilityChanged) {
        this.addToIndices(
          id,
          changes.type !== undefined ? changes.type : billboard.type,
          visibilityChanged ? newBillboard.visible : billboard.visible
        )
      }

      this.billboards.set(id, newBillboard)
    }
  }

  /**
   * 根据 id 删除单个 Billboard
   * @param id
   * @returns
   */
  remove(id: string): void {
    const billboard = this.get(id)
    if (!billboard) {
      console.warn('Billboard with id ${id} not found')
      return
    }

    this.billboardCollection.remove(billboard.cesiumBillboard)
    this.removeFromIndices(id, billboard.type, billboard.visible)
    this.billboards.delete(id)
  }

  /**
   * 批量添加 Billboard
   * @param items
   */
  batchAdd(items: Array<Record<string, any>>): void {
    console.log('🚀 ~ BillboardController ~ batchAdd ~ items:', items)
    for (const item of items) {
      this.add(item)
    }
  }

  /**
   * 批量删除 Billboard
   * @param ids
   */
  batchRemove(ids: string[]): void {
    for (const id of ids) {
      this.remove(id)
    }
  }

  /**
   * 批量更新 Billboard
   * @param ids
   * @param changes
   */
  batchUpdate(
    ids: string[],
    changes: Partial<{
      type?: string
      visible?: boolean
      options?: Partial<Cesium.Billboard.ConstructorOptions>
      metadata?: Record<string, any>
    }>
  ): void {
    for (const id of ids) {
      this.update(id, changes)
    }
  }

  /**
   * 根据类型获取所有 Billboard
   * @param type
   * @returns
   */
  getByType(type: string): TypedBillboard[] {
    const ids = this.typeIndex.get(type)
    return ids
      ? Array.from(ids)
          .map((id) => this.get(id)!)
          .filter((b) => b)
      : []
  }

  /**
   * 获取所有类型
   * @returns
   */
  getAllTypes(): string[] {
    return Array.from(this.typeIndex.keys())
  }

  /**
   * 根据类型设置可见性
   * @param type
   * @param visible
   * @returns
   */
  setVisibilityByType(type: string, visible: boolean): void {
    const ids = this.typeIndex.get(type)
    if (!ids?.size) return
    this.batchUpdate(Array.from(ids), { visible })
  }

  /**
   * 获取可见的 Billboard
   * @returns
   */
  getVisible(): TypedBillboard[] {
    return this.getByVisibility(true)
  }

  /**
   * 获取不可见的 Billboard
   * @returns
   */
  getHidden(): TypedBillboard[] {
    return this.getByVisibility(false)
  }

  /**
   * 根据可见性获取 Billboard
   * @param visible
   * @returns
   */
  private getByVisibility(visible: boolean): TypedBillboard[] {
    const ids = this.visibilityIndex.get(visible)
    return ids
      ? Array.from(ids)
          .map((id) => this.get(id)!)
          .filter((b) => b)
      : []
  }

  /**
   * 设置所有 Billboard 的可见性
   * @param visible
   */
  setAllVisibility(visible: boolean): void {
    this.batchUpdate(Array.from(this.billboards.keys()), { visible })
  }

  /**
   * 添加或更新单个 Billboard
   * @param id
   * @param type
   * @param visible
   */
  private addToIndices(id: string, type: string, visible: boolean): void {
    if (!this.typeIndex.has(type)) {
      this.typeIndex.set(type, new Set())
    }
    this.typeIndex.get(type)!.add(id)
    this.visibilityIndex.get(visible)!.add(id)
    this.visibilityIndex.get(!visible)!.delete(id)
  }

  /**
   * 从索引中移除单个 Billboard
   * @param id
   * @param type
   * @param visible
   */
  private removeFromIndices(id: string, type: string, visible: boolean): void {
    this.typeIndex.get(type)?.delete(id)
    if (this.typeIndex.get(type)?.size === 0) {
      this.typeIndex.delete(type)
    }
    this.visibilityIndex.get(visible)?.delete(id)
  }

  /**
   * 获取所有 Billboard
   * @returns
   */
  getAll(): TypedBillboard[] {
    return Array.from(this.billboards.values())
  }

  /**
   * 清除所有 Billboard
   * @returns
   */
  clear(): void {
    this.billboardCollection.removeAll()
    this.billboards.clear()
    this.typeIndex.clear()
    this.visibilityIndex.forEach((set) => set.clear())
  }

  /**
   * 销毁 BillboardController 实例
   */
  destroy(): void {
    this.clear()
    if (this.billboardCollection && !this.billboardCollection.isDestroyed()) {
      this.viewer.scene.primitives.remove(this.billboardCollection)
    }
  }

  /**
   * 查找所有符合条件的 Billboard
   * @param predicate 判断函数，返回 true 表示匹配
   * @returns 匹配的所有 Billboard 数组
   */
  find(predicate: (billboard: TypedBillboard) => boolean): TypedBillboard[] {
    return Array.from(this.billboards.values()).filter(predicate)
  }
}
